/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Reflection;

namespace DaveSexton.DocProject
{
	[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Flags")]
	public class EnumFlagsConverter : EnumConverter
	{
		#region Public Properties
		#endregion

		#region Private / Protected
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="EnumFlagsConverter" /> class.
		/// </summary>
		public EnumFlagsConverter(Type type)
			: base(type)
		{
		}
		#endregion

		#region Methods
		public override bool GetPropertiesSupported(ITypeDescriptorContext context)
		{
			if (context != null)
			{
				PropertyDescriptor property = context.PropertyDescriptor;

				if (property != null)
				{
					AttributeCollection attributes = context.PropertyDescriptor.Attributes;

					if (attributes != null && attributes.Matches(new FlagsAttribute()))
						return true;
				}
			}

			return false;
		}

		public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
		{
			List<PropertyDescriptor> properties = new List<PropertyDescriptor>();

			Type type = value.GetType();
			string[] names = Enum.GetNames(type);
			Array values = Enum.GetValues(type);

			BooleanValuesAttribute[] booleansAttributes =
				(BooleanValuesAttribute[]) type.GetCustomAttributes(typeof(BooleanValuesAttribute), false);

			string trueValue = null;

			if (booleansAttributes != null && booleansAttributes.Length > 0)
			{
				BooleanValuesAttribute booleansAttribute = booleansAttributes[0];

				if (booleansAttribute != null)
					trueValue = booleansAttribute.TrueValue;
			}

			for (int i = 0; i < names.Length; i++)
			{
				string name = names[i];

				long val = ((IConvertible) values.GetValue(i)).ToInt64(System.Globalization.CultureInfo.InvariantCulture);

				List<Attribute> flagAttributes = new List<Attribute>(attributes);

				object[] objAttributes = type.GetField(name, BindingFlags.Public | BindingFlags.Static).GetCustomAttributes(false);

				foreach (object objAttribute in objAttributes)
				{
					Attribute attribute = objAttribute as Attribute;

					if (attribute != null && !flagAttributes.Exists(delegate(Attribute listedAttribute)
					{
						return attribute.Match(listedAttribute);
					}))
						flagAttributes.Add(attribute);
				}

				flagAttributes.Add(new RefreshPropertiesAttribute(RefreshProperties.Repaint));

				properties.Add(new EnumValueDescriptor(type, context, name, val, flagAttributes.ToArray(), trueValue));
			}

			return new PropertyDescriptorCollection(properties.ToArray());
		}
		#endregion

		#region Nested
		[TypeConverter(typeof(FlagStateConverter))]
		private enum FlagState
		{
			False = 0, 
			True = 1
		}

		private sealed class FlagStateConverter : EnumConverter
		{
			#region Private / Protected
			private bool overrideNames, initialized;
			private string trueValue, falseValue;
			#endregion

			#region Constructors
			/// <summary>
			/// Constructs a new instance of the <see cref="FlagStateConverter" /> class.
			/// </summary>
			public FlagStateConverter(Type type)
				: base(type)
			{
			}
			#endregion

			#region Methods
			private void Initialize(ITypeDescriptorContext context)
			{
				if (context != null)
				{
					object property = context.Instance;

					if (property != null)
					{
						Type type = property.GetType();

						BooleanValuesAttribute[] booleansAttributes =
							(BooleanValuesAttribute[]) type.GetCustomAttributes(typeof(BooleanValuesAttribute), false);
 
						if (booleansAttributes != null && booleansAttributes.Length > 0)
						{
							BooleanValuesAttribute booleansAttribute = booleansAttributes[0];

							if (booleansAttribute != null)
							{
								trueValue = booleansAttribute.TrueValue;
								falseValue = booleansAttribute.FalseValue;

								overrideNames = true;
							}
						}
					}
				}

				initialized = true;
			}

			public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
			{
				if (!initialized)
					Initialize(context);

				return overrideNames || base.GetStandardValuesSupported(context);
			}

			public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
			{
				if (!initialized)
					Initialize(context);

				if (overrideNames)
					return new StandardValuesCollection(new string[] { falseValue, trueValue });
				else
					return base.GetStandardValues(context);
			}

			public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
			{
				if (!initialized)
					Initialize(context);

				if (overrideNames)
					return true;
				else
					return base.GetStandardValuesExclusive(context);
			}

			public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
			{
				if (!initialized)
					Initialize(context);

				if (overrideNames)
					return (string.Equals(value as string, trueValue, StringComparison.OrdinalIgnoreCase)) ? 1 : 0;
				else
					return base.ConvertFrom(context, culture, value);
			}

			public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
			{
				if (!initialized)
					Initialize(context);

				if (destinationType == typeof(string))
				{
					if (value is FlagState || value is int)
						return ((int) value == 0) ? falseValue : trueValue;
					else if (value == null)
						return falseValue;
					else
						return (string.Equals(trueValue, value.ToString(), StringComparison.OrdinalIgnoreCase)) ? trueValue : falseValue;
				}
				else
					return base.ConvertTo(context, culture, value, destinationType);
			}
			#endregion
		}

		private sealed class EnumValueDescriptor : PropertyDescriptor
		{
			#region Public Properties
			public override Type ComponentType
			{
				get
				{
					return enumType;
				}
			}

			public override bool IsReadOnly
			{
				get
				{
					return false;
				}
			}

			public override Type PropertyType
			{
				get
				{
					return typeof(FlagState);
				}
			}
			#endregion

			#region Private / Protected
			private readonly long flagValue;
			private readonly Type enumType;
			private readonly FieldInfo valueField;
			private readonly ITypeDescriptorContext context;
			private readonly string trueValue;
			#endregion

			#region Constructors
			/// <summary>
			/// Constructs a new instance of the <see cref="EnumValueDescriptor" /> class.
			/// </summary>
			public EnumValueDescriptor(Type enumType, ITypeDescriptorContext context, string name, long flagValue, Attribute[] attributes, string trueValue)
				: base(name, attributes)
			{
				this.enumType = enumType;
				this.context = context;
				this.flagValue = flagValue;
				this.trueValue = trueValue;

				valueField = enumType.GetField("value__", BindingFlags.Public | BindingFlags.Instance);
			}
			#endregion
			
			#region Methods
			private static long ToInt64(object component)
			{
				return ((IConvertible) component).ToInt64(System.Globalization.CultureInfo.InvariantCulture);
			}

			public override bool CanResetValue(object component)
			{
				return true;
			}

			public override object GetValue(object component)
			{
				bool value;

				if (flagValue == 0)
					value = ToInt64(component) == 0;
				else
					value = (ToInt64(component) & flagValue) == flagValue;

				return (value) ? FlagState.True : FlagState.False;
			}

			public override void ResetValue(object component)
			{
				SetValue(component, FlagState.False);
			}

			public override void SetValue(object component, object value)
			{
				long val = ToInt64(component);

				string strValue = value as string;

				if (strValue != null)
					value = (string.Equals(trueValue, strValue, StringComparison.OrdinalIgnoreCase)) ? FlagState.True : FlagState.False;

				long enumValue = (flagValue == 0) ? 0 :
					((FlagState) value == FlagState.True)
						? val | flagValue
						: val & ~flagValue;

				valueField.SetValue(component, Convert.ChangeType(enumValue, Enum.GetUnderlyingType(enumType), System.Globalization.CultureInfo.InvariantCulture));

				context.PropertyDescriptor.SetValue(context.Instance, component);
			}

			public override bool ShouldSerializeValue(object component)
			{
				return (FlagState) GetValue(component) == FlagState.True;
			}
			#endregion
		}
		#endregion
	}
}
